﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Xml.Linq;
using System.Text;
using System.IO;
using System.Reflection;

namespace JmrToLinqToSqlGenerator
{
	public class JmrToLinqToSqlActiveRecordGenerator
	{
		public JmrToLinqToSqlActiveRecordGenerator() { }

		public void Generate(string dbmlPath, string dbmlCsPath, string outputPathEntities, string outputPathBusiness, string csproj, bool makeSerializable, string version)
		{
			GenerateEntities(dbmlPath, dbmlCsPath, outputPathEntities, csproj, makeSerializable, version, "Entities");
			//GenerateBusiness(dbmlPath, dbmlCsPath, outputPathBusiness, csproj, makeSerializable, version, "Entities", "Business");
		}
		
		public void GenerateEntities(string dbmlPath, string dbmlCsPath, string outputPath, string csproj, bool makeSerializable, string version, string nameSpace)
		{
			AssimilateDbmlCs(dbmlCsPath, makeSerializable);

			Console.WriteLine("Generating entities file: {0}", outputPath);

			var sb = new StringBuilder();
			var meta = new DbmlReader().Load(dbmlPath);

			BeginPragmaDisableSection(sb);
			FormatAutoGeneratedSection(sb, version);
			sb.AppendLine();

			FormatUsingSection(sb, csproj);
			sb.AppendLine();
			
			BeginNamespaceSection(sb, meta, nameSpace);

			foreach (var entityMeta in meta.Entities)
			{
				FormatClassSection(sb, entityMeta, meta, makeSerializable);
				sb.AppendLine();
				FormatCollectionSection(sb, entityMeta, meta, makeSerializable);
				sb.AppendLine();
			}

			foreach (var func in meta.Functions)
			{
				FormatFunctionClassSection(sb, func, meta, makeSerializable);
				sb.AppendLine();
				FormatFunctionCollectionSection(sb, func, meta, makeSerializable);
				sb.AppendLine();
			}

			EndNamespaceSection(sb);
			EndPragmaDisableSection(sb);

			#if (DEBUG)
			Console.WriteLine(sb.ToString());
			Console.ReadLine();
			#endif

			var sw = new StreamWriter(outputPath, false);
			sw.Write(sb.ToString());
			sw.Close();
			sw = null;

			Console.WriteLine("Succesfully generated entities file.");
		}

		public void GenerateBusiness(string dbmlPath, string dbmlCsPath, string outputPath, string csproj, bool makeSerializable, string version, string nameSpaceEntities, string nameSpaceBusiness)
		{
			Console.WriteLine("Generating business entities file: {0}", outputPath);
			
			var sb = new StringBuilder();
			var meta = new DbmlReader().Load(dbmlPath);

			FormatUsingSection(sb, csproj);
			sb.AppendLine();

			BeginNamespaceSection(sb, meta, nameSpaceBusiness);

			foreach (var entityMeta in meta.Entities)
			{
				FormatBusClassSection(sb, entityMeta, meta, makeSerializable, nameSpaceEntities);
				sb.AppendLine();
				FormatBusCollectionSection(sb, entityMeta, meta, makeSerializable, nameSpaceEntities);
				sb.AppendLine();
			}

			foreach (var func in meta.Functions)
			{
				FormatFunctionBusClassSection(sb, func, meta, makeSerializable, nameSpaceEntities);
				sb.AppendLine();
				FormatFunctionBusCollectionSection(sb, func, meta, makeSerializable, nameSpaceEntities);
				sb.AppendLine();
			}

			EndNamespaceSection(sb);

			#if (DEBUG)
			Console.WriteLine(sb.ToString());
			Console.ReadLine();
			#endif

			var idx = 1;
			while (File.Exists(outputPath))
			{
				var strarr = outputPath.Split(new char[] {'.'});
				outputPath = outputPath.Replace(strarr[1], "Business" + idx.ToString());
				idx++;
			}

			var sw = new StreamWriter(outputPath, false);
			sw.Write(sb.ToString());
			sw.Close();
			sw = null;

			Console.WriteLine("Succesfully generated business entities file.");
		}

		private void FormatBusClassSection(StringBuilder sb, EntityMeta entityMeta, ContextMeta meta, bool makeSerializable, string nameSpace)
		{
			Console.Write("Generating {0} business entity class...", entityMeta.JmrBusinessClassName);

			if (makeSerializable)
				sb.AppendLine("\t[Serializable]");

			sb.AppendLine(string.Format("\tpublic partial class {0} : {1}.{2}.{0}", entityMeta.JmrBusinessClassName, meta.DatabaseName, nameSpace));
			sb.AppendLine("\t{");
			sb.AppendLine(string.Format("\t\tpublic {0}() : base() {{ }}", entityMeta.JmrBusinessClassName));
			sb.AppendLine("\t}");

			Console.WriteLine("Done!");
		}

		private void FormatBusCollectionSection(StringBuilder sb, EntityMeta entityMeta, ContextMeta meta, bool makeSerializable, string nameSpace)
		{
			Console.Write("Generating {0}Collection business entity collection class...", entityMeta.JmrBusinessClassName);

			if (makeSerializable)
				sb.AppendLine("\t[Serializable]");

			sb.AppendLine(string.Format("\tpublic partial class {0}Collection : {1}.{2}.{0}Collection", entityMeta.JmrBusinessClassName, meta.DatabaseName, nameSpace));
			sb.AppendLine("\t{");
			sb.AppendLine(string.Format("\t\tpublic {0}Collection() : base() {{ }}", entityMeta.JmrBusinessClassName));
			sb.AppendLine("\t}");

			Console.WriteLine("Done!");
		}

		private void FormatFunctionBusClassSection(StringBuilder sb, FunctionMeta funcMeta, ContextMeta contextMeta, bool makeSerializable, string nameSpace)
		{
			Console.Write("Generating {0} business entity class...", funcMeta.JmrElementTypeName);

			if (!string.IsNullOrEmpty(funcMeta.LinqEntityIdRef)) { return; }

			if (makeSerializable)
				sb.AppendLine("\t[Serializable]");

			sb.AppendLine(string.Format("\tpublic partial class {0} : {1}.{2}.{0}", funcMeta.JmrElementTypeName, contextMeta.DatabaseName, nameSpace));
			sb.AppendLine("\t{");
			sb.AppendLine(string.Format("\t\tpublic {0}() : base() {{ }}", funcMeta.JmrElementTypeName));
			sb.AppendLine("\t}");

			Console.WriteLine("Done!");
		}

		private void FormatFunctionBusCollectionSection(StringBuilder sb, FunctionMeta funcMeta, ContextMeta contextMeta, bool makeSerializable, string nameSpace)
		{
			Console.Write("Generating {0} business entity class...", funcMeta.JmrMethodName);

			if (!string.IsNullOrEmpty(funcMeta.LinqEntityIdRef))
			{
				funcMeta.JmrElementTypeName = (from e in contextMeta.Entities
																			 where e.LinqIdRef == funcMeta.LinqEntityIdRef
																			 select e).Single().JmrBusinessClassName;
			}

			if (makeSerializable)
				sb.AppendLine("\t[Serializable]");

			sb.AppendLine(string.Format("\tpublic partial class {0} : {1}.{2}.{0}", funcMeta.JmrMethodName, contextMeta.DatabaseName, nameSpace));
			sb.AppendLine("\t{");
			sb.AppendLine(string.Format("\t\tpublic {0}() : base() {{ }}", funcMeta.JmrMethodName));
			sb.AppendLine("\t}");

			Console.WriteLine("Done!");
		}

		private void AssimilateDbmlCs(string dbmlCsPath, bool makeSerializable)
		{
			if (dbmlCsPath == null) { return; }
			Console.WriteLine("Modifying file: {0}", dbmlCsPath);

			var stream = File.OpenText(dbmlCsPath);
			var sb = new StringBuilder(stream.ReadToEnd());
			stream.Dispose();

			//sb.Replace("[Table(", "[Serializable, Table(");
			//sb.Replace(" : INotifyPropertyChanging, INotifyPropertyChanged", string.Empty);

			sb.Replace(", UpdateCheck=UpdateCheck.Always", string.Empty);
			sb.Replace("UpdateCheck=UpdateCheck.Always, ", string.Empty);

			sb.Replace(", UpdateCheck=UpdateCheck.WhenChanged", string.Empty);
			sb.Replace("UpdateCheck=UpdateCheck.WhenChanged, ", string.Empty);

			sb.Replace(", UpdateCheck=UpdateCheck.Never", string.Empty);
			sb.Replace("UpdateCheck=UpdateCheck.Never, ", string.Empty);

			sb.Replace("[Column(", "[Column(UpdateCheck=UpdateCheck.Never, ");

			var sw = new StreamWriter(dbmlCsPath, false);
			sw.Write(sb.ToString());
			sw.Close();
			sw = null;

			Console.WriteLine("Succesfully modified file.");
		}

		private void BeginPragmaDisableSection(StringBuilder sb)
		{
			sb.AppendLine("#pragma warning disable 1591");
		}

		private void EndPragmaDisableSection(StringBuilder sb)
		{
			sb.AppendLine("#pragma warning restore 1591");
		}

		private void FormatAutoGeneratedSection(StringBuilder sb, string version)
		{
			sb.AppendLine("//------------------------------------------------------------------------------");
			sb.AppendLine("// <auto-generated>");
			sb.AppendLine("//     This code was generated by the JmrToLinqToSqlGenerator tool.");
			sb.AppendLine(string.Format("//     JmrToLinqToSqlGenerator Tool Version:{0}", version));
			sb.AppendLine("//");
			sb.AppendLine("//     Changes to this file may cause incorrect behavior and will be lost if");
			sb.AppendLine("//     the code is regenerated.");
			sb.AppendLine("// </auto-generated>");
			sb.AppendLine("//------------------------------------------------------------------------------");
		}

		private void FormatUsingSection(StringBuilder sb, string csproj)
		{
			sb.AppendLine("using System;");
			sb.AppendLine("using System.Linq;");
			sb.AppendLine("using System.Collections.Generic;");
			sb.AppendLine("using System.Collections.ObjectModel;");
			sb.AppendLine("using Jmr.Entity35;");
			sb.AppendLine("using Jmr.Entity35.Validation;");
			
			if (!string.IsNullOrEmpty(csproj))
				sb.AppendLine(string.Format("using {0};", csproj.Replace(".csproj", string.Empty)));
		}

		private void BeginNamespaceSection(StringBuilder sb, ContextMeta meta, string nameSpace)
		{
			sb.AppendLine(string.Format("namespace {0}.{1}", meta.DatabaseName, nameSpace));
			sb.AppendLine("{");
		}

		private void EndNamespaceSection(StringBuilder sb)
		{
			sb.AppendLine("}");
		}

		private void FormatClassSection(StringBuilder sb, EntityMeta entityMeta, ContextMeta meta, bool makeSerializable)
		{
			Console.Write("Generating {0} entity class...", entityMeta.JmrBusinessClassName);

			if (makeSerializable)
				sb.AppendLine("\t[Serializable]");

			sb.AppendLine(string.Format("\tpublic partial class {0} : IEntityActiveRecord, IValidateable", entityMeta.JmrBusinessClassName));
			sb.AppendLine("\t{");
			
			FormatClassConstructorSection(sb, entityMeta);
			sb.AppendLine();
			FormatClassContextSection(sb, meta);
			sb.AppendLine();
			FormatLinqEntitySection(sb, entityMeta);

			sb.AppendLine();
			sb.AppendLine("\t\t#region Properties");
			sb.AppendLine();
			foreach (var propMeta in entityMeta.Properties)
			{
				FormatClassPropertySection(sb, propMeta, true);
				sb.AppendLine();
			}
			sb.AppendLine();
			sb.AppendLine("\t\t#endregion");
			sb.AppendLine();
			
			sb.AppendLine("\t\t#region Associations");
			sb.AppendLine();
			FormatParentCollectionSection(sb, entityMeta);
			sb.AppendLine();

			foreach (var propMeta in entityMeta.Associations)
			{
				FormatClassAssociationSection(sb, propMeta, entityMeta);
				sb.AppendLine();
			}
			sb.AppendLine();
			sb.AppendLine("\t\t#endregion");
			sb.AppendLine();
			
			FormatIEntitySection(sb, entityMeta, meta);
			sb.AppendLine();
			
			FormatIValidateableSection(sb);
			sb.AppendLine("\t}");
			
			Console.WriteLine("Done!");
		}

		private void FormatClassConstructorSection(StringBuilder sb, EntityMeta meta)
		{
			sb.AppendLine(string.Format("\t\tpublic {0}()", meta.JmrBusinessClassName));
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tValidators.Add(new SimpleValidator());");
			sb.AppendLine("\t\t}");
		}

		private void FormatClassContextSection(StringBuilder sb, ContextMeta meta)
		{
			sb.AppendLine("\t\t[NonSerialized]");
			sb.AppendLine(string.Format("\t\tprivate {0} _ctx = null;", meta.ContextClassName));
			sb.AppendLine(string.Format("\t\tpublic {0} Context", meta.ContextClassName));
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tget");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine("\t\t\t\tif (_ctx == null)");
			sb.AppendLine(string.Format("\t\t\t\t\t_ctx = new {0}();", meta.ContextClassName));
			sb.AppendLine();
			sb.AppendLine("\t\t\t\treturn _ctx;");
			sb.AppendLine("\t\t\t}");
			sb.AppendLine("\t\t\tset");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine("\t\t\t\t_ctx = value;");
			sb.AppendLine("\t\t\t}");
			sb.AppendLine("\t\t}");
		}

		private void FormatLinqEntitySection(StringBuilder sb, EntityMeta meta)
		{
			sb.AppendLine(string.Format("\t\tprivate {0} _linqEntity = null;", meta.LinqEntityClassName));
			sb.AppendLine(string.Format("\t\tinternal {0} LinqEntity", meta.LinqEntityClassName));
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tget");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine("\t\t\t\tif (_linqEntity == null)");
			sb.AppendLine(string.Format("\t\t\t\t\t_linqEntity = new {0}();", meta.LinqEntityClassName));
			sb.AppendLine();
			sb.AppendLine("\t\t\t\treturn _linqEntity;");
			sb.AppendLine("\t\t\t}");
			sb.AppendLine("\t\t\tset");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine("\t\t\t\t_linqEntity = value;");
			sb.AppendLine("\t\t\t}");
			sb.AppendLine("\t\t}");
		}

		private void FormatClassPropertySection(StringBuilder sb, EntityPropertyMeta meta, bool implementHasChanges)
		{
			FormatValidationAttribute(sb, meta);
			sb.AppendLine(string.Format("\t\tpublic virtual {0} {1}", meta.ClrType, meta.JmrPropertyName));
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tget");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine(string.Format("\t\t\t\treturn LinqEntity.{0};", meta.LinqPropertyName));
			sb.AppendLine("\t\t\t}");
			sb.AppendLine("\t\t\tset");
			sb.AppendLine("\t\t\t{");
			
			if (implementHasChanges)
				sb.AppendLine(string.Format("\t\t\t\tHasChanges = (LinqEntity.{0} != value);", meta.LinqPropertyName));
			
			sb.AppendLine(string.Format("\t\t\t\tLinqEntity.{0} = value;", meta.LinqPropertyName));
			sb.AppendLine("\t\t\t}");
			sb.AppendLine("\t\t}");
		}

		private void FormatValidationAttribute(StringBuilder sb, EntityPropertyMeta meta)
		{
			if (!meta.IsNullable)
				sb.AppendLine(string.Format("\t\t[Required(\"'{0}' is a required field.\")]", meta.JmrPropertyName));

			switch (meta.RawClrType)
			{
				case "System.Char":
				case "System.String":
					if (meta.Scale != -1)
						sb.AppendLine(string.Format("\t\t[MaxLength({0}, \"The '{1}' field cannot accept more than {0} characters.\")]", meta.Scale, meta.JmrPropertyName));
					break;
			}
		}

		private void FormatParentCollectionSection(StringBuilder sb, EntityMeta entityMeta)
		{
			sb.AppendLine(string.Format("\t\tpublic {0}Collection ParentCollection", entityMeta.JmrBusinessClassName));
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tget");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine("\t\t\t\treturn _parentCollection;");
			sb.AppendLine("\t\t\t}");
			sb.AppendLine("\t\t\tinternal set");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine("\t\t\t\t_parentCollection = value;");
			sb.AppendLine("\t\t\t} ");
			sb.AppendLine("\t\t}");
			sb.AppendLine(string.Format("\t\tprivate {0}Collection _parentCollection = null;", entityMeta.JmrBusinessClassName));
			sb.AppendLine();
			sb.AppendLine("\t\tpublic bool HasParentCollection");
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tget");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine("\t\t\t\treturn (ParentCollection != null);");
			sb.AppendLine("\t\t\t}");
			sb.AppendLine("\t\t}");
		}

		private void FormatClassAssociationSection(StringBuilder sb, EntityAssociationMeta assocMeta, EntityMeta entityMeta)
		{
			if (assocMeta.IsParent)
			{
				sb.AppendLine(string.Format("\t\tpublic virtual {0}Collection {1}", assocMeta.JmrEntityName, assocMeta.JmrCollectionMemberName));
				sb.AppendLine("\t\t{");
				sb.AppendLine("\t\t\tget");
				sb.AppendLine("\t\t\t{");
				sb.AppendLine(string.Format("\t\t\t\tif (_{0} == null)", assocMeta.JmrCollectionMemberName));
				sb.AppendLine("\t\t\t\t{");
				sb.AppendLine(string.Format("\t\t\t\t\t_{0} = new {1}Collection();", assocMeta.JmrCollectionMemberName, assocMeta.JmrEntityName));
				sb.AppendLine(string.Format("\t\t\t\t\t_{0}.Context = Context;", assocMeta.JmrCollectionMemberName, assocMeta.JmrEntityName));
				sb.AppendLine();
				sb.AppendLine(string.Format("\t\t\t\t\tforeach (var item in LinqEntity.{0})", assocMeta.LinqCollectionMemberName));
				sb.AppendLine("\t\t\t\t\t{");
				sb.AppendLine(string.Format("\t\t\t\t\t\tvar entity = new {0}();", assocMeta.JmrEntityName));
				sb.AppendLine("\t\t\t\t\t\tentity.LinqEntity = item;");
				sb.AppendLine("\t\t\t\t\t\tentity.LoadedFromDb = true;");
				sb.AppendLine(string.Format("\t\t\t\t\t\t_{0}.Add(entity);", assocMeta.JmrCollectionMemberName));
				sb.AppendLine("\t\t\t\t\t}");
				sb.AppendLine("\t\t\t\t}");
				sb.AppendLine();
				sb.AppendLine(string.Format("\t\t\t\treturn _{0};", assocMeta.JmrCollectionMemberName));
				sb.AppendLine("\t\t\t}");
				sb.AppendLine("\t\t}");
				sb.AppendLine(string.Format("\t\tprivate {0}Collection _{1} = null;", assocMeta.JmrEntityName, assocMeta.JmrCollectionMemberName));
				sb.AppendLine();
				sb.AppendLine(string.Format("\t\tprivate bool Is{0}Loaded", assocMeta.JmrCollectionMemberName));
				sb.AppendLine("\t\t{");
				sb.AppendLine("\t\t\tget");
				sb.AppendLine("\t\t\t{");
				sb.AppendLine(string.Format("\t\t\t\treturn (_{0} != null);", assocMeta.JmrCollectionMemberName));
				sb.AppendLine("\t\t\t}");
				sb.AppendLine("\t\t}");

				//sb.AppendLine();
				//sb.AppendLine(string.Format("\t\tpublic static {0}Collection Get{1}By{2}", assocMeta.JmrEntityName, assocMeta.JmrCollectionMemberName, entityMeta.JmrBusinessClassName));
				//sb.AppendLine("\t\t{");
				//sb.AppendLine(string.Format("\t\t\tvar ret = new {0}Collection();", assocMeta.JmrEntityName));
				//sb.AppendLine(string.Format("\t\t\tforeach (var ent in ;", assocMeta.JmrEntityName));
				//sb.AppendLine("\t\t}");
			}
			else
			{
				sb.AppendLine(string.Format("\t\tpublic virtual {0} {0}", assocMeta.JmrEntityName));
				sb.AppendLine("\t\t{");
				sb.AppendLine("\t\t\tget");
				sb.AppendLine("\t\t\t{");
				sb.AppendLine(string.Format("\t\t\t\tif (_{0} == null)", assocMeta.JmrEntityName));
				sb.AppendLine(string.Format("\t\t\t\t\t_{0} = new {0}();", assocMeta.JmrEntityName));
				sb.AppendLine();
				sb.AppendLine(string.Format("\t\t\t\t_{0}.LinqEntity = LinqEntity.{1};", assocMeta.JmrEntityName, assocMeta.LinqEntityName));
				sb.AppendLine(string.Format("\t\t\t\treturn _{0};", assocMeta.JmrEntityName));
				sb.AppendLine("\t\t\t}");
				sb.AppendLine("\t\t}");
				sb.AppendLine(string.Format("\t\tprivate {0} _{0} = null;", assocMeta.JmrEntityName));
				sb.AppendLine();
				sb.AppendLine(string.Format("\t\tprivate bool Is{0}Loaded", assocMeta.JmrCollectionMemberName));
				sb.AppendLine("\t\t{");
				sb.AppendLine("\t\t\tget");
				sb.AppendLine("\t\t\t{");
				sb.AppendLine(string.Format("\t\t\t\treturn (_{0} != null);", assocMeta.JmrCollectionMemberName));
				sb.AppendLine("\t\t\t}");
				sb.AppendLine("\t\t}");
			}
		}

		private void FormatIEntitySection(StringBuilder sb, EntityMeta entityMeta, ContextMeta ctxmeta)
		{
			var keyProps = from prop in entityMeta.Properties
										 where prop.IsKey
										 select prop;

			sb.AppendLine("\t\t#region IEntity Members");
			sb.AppendLine();
			sb.AppendLine("\t\tpublic bool HasParentContext { get; internal set; }");
			sb.AppendLine();
			sb.AppendLine("\t\tpublic bool HasChanges { get; internal set; }");
			sb.AppendLine();
			sb.AppendLine("\t\tpublic bool LoadedFromDb { get; internal set; }");
			sb.AppendLine();
			sb.AppendLine("\t\tpublic event EntityEventHander OnBeforeSave;");
			sb.AppendLine();
			sb.AppendLine("\t\tpublic event EntityEventHander OnAfterSave;");
			sb.AppendLine();
			sb.AppendLine("\t\tpublic event EntityEventHander OnBeforeDelete;");
			sb.AppendLine();
			sb.AppendLine("\t\tpublic event EntityEventHander OnAfterDelete;");
			sb.AppendLine();
			sb.AppendLine(string.Format("\t\tinternal void AttachParentContext({0} context)", ctxmeta.ContextClassName));
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tContext = context;");
			sb.AppendLine("\t\t\tHasParentContext = true;");
			sb.AppendLine("\t\t}");
			sb.AppendLine();
			sb.AppendLine("\t\tinternal void DetachParentContext()");
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tContext = null;");
			sb.AppendLine("\t\t\tHasParentContext = false;");
			sb.AppendLine("\t\t}");
			sb.AppendLine();

			var methodSignature = string.Empty;
			if (keyProps.Count() > 0)
				foreach (var prop in keyProps)
					methodSignature += string.Format("{0} {1}{2}, ", prop.ClrType, prop.JmrPropertyName.Substring(0, 1).ToLower(), prop.JmrPropertyName.Substring(1, prop.JmrPropertyName.Length-1));

			sb.AppendLine(string.Format("\t\tpublic static {0} Get{0}By({1})", entityMeta.JmrBusinessClassName, methodSignature.Substring(0, methodSignature.Length-2)));
			sb.AppendLine("\t\t{");
			sb.AppendLine(string.Format("\t\t\t{0} ret = new {0}();", entityMeta.JmrBusinessClassName));
			sb.AppendLine(string.Format("\t\t\t{0} ctx = new {0}();", ctxmeta.ContextClassName));
			sb.AppendLine("\t\t\tret.Context = ctx;");

			if (keyProps.Count() > 0)
			{
				var whereClause = string.Empty;
				foreach (var prop in keyProps)
					whereClause += string.Format("ent.{0} == {1}{2} && ", prop.LinqPropertyName, prop.JmrPropertyName.Substring(0, 1).ToLower(), prop.JmrPropertyName.Substring(1, prop.JmrPropertyName.Length - 1));
				
				sb.AppendLine(string.Format("\t\t\tret.LinqEntity = ctx.{1}.Single(ent => {2});", ctxmeta.ContextClassName, entityMeta.LinqEntityCollectionName, whereClause.Substring(0, whereClause.Length - 4)));
				sb.AppendLine("\t\t\tret.LoadedFromDb = true;");
				sb.AppendLine("\t\t\treturn ret;");
			}
			else
			{
				sb.AppendLine("\t\t\tthrow new System.Exception(\"JmrToLinqToSqlGenerator could not implement this method.  Missing primary key column.\");");
			}

			sb.AppendLine("\t\t}");
			sb.AppendLine();
			sb.AppendLine("\t\tpublic virtual void Save()");
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tif (OnBeforeSave != null) { OnBeforeSave(this); }");
			sb.AppendLine();
			sb.AppendLine(string.Format("\t\t\tif (HasChanges && LoadedFromDb)", entityMeta.LinqEntityCollectionName));
			sb.AppendLine("\t\t\t{");
			sb.AppendLine(string.Format("\t\t\t\tif (!Context.GetChangeSet().Updates.Contains(LinqEntity))", entityMeta.LinqEntityCollectionName));
			sb.AppendLine(string.Format("\t\t\t\t\tContext.{0}.Attach(LinqEntity, HasChanges);", entityMeta.LinqEntityCollectionName));
			sb.AppendLine("\t\t\t}");
			sb.AppendLine();
			sb.AppendLine("\t\t\tif (!LoadedFromDb)");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine(string.Format("\t\t\t\tif (!Context.GetChangeSet().Inserts.Contains(LinqEntity))", entityMeta.LinqEntityCollectionName));
			sb.AppendLine(string.Format("\t\t\t\t\tContext.{0}.InsertOnSubmit(LinqEntity);", entityMeta.LinqEntityCollectionName));
			sb.AppendLine("\t\t\t}");
			sb.AppendLine();
			sb.AppendLine("\t\t\t#region Associations");
			sb.AppendLine();

			if (entityMeta.Associations.Count > 0)
			{
				foreach (var assoc in entityMeta.Associations)
				{
					var entName = (assoc.IsParent) ? assoc.JmrCollectionMemberName : assoc.JmrEntityName;
					sb.AppendLine(string.Format("\t\t\tif (Is{0}Loaded)", entName));
					sb.AppendLine("\t\t\t{");
					sb.AppendLine(string.Format("\t\t\t\t{0}.AttachParentContext(Context);", entName));
					sb.AppendLine(string.Format("\t\t\t\t{0}.Save();", entName));
					sb.AppendLine(string.Format("\t\t\t\t{0}.DetachParentContext();", entName));
					sb.AppendLine("\t\t\t}");
					sb.AppendLine();
				}
			}

			sb.AppendLine("\t\t\t#endregion Associations");
			sb.AppendLine();
			sb.AppendLine("\t\t\tContextSubmitChanges();");
			sb.AppendLine();
			sb.AppendLine("\t\t\tif (OnAfterSave != null) { OnAfterSave(this); }");
			sb.AppendLine("\t\t}");
			sb.AppendLine();
			sb.AppendLine("\t\tpublic virtual void Delete()");
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tif (OnBeforeDelete != null) { OnBeforeDelete(this); }");
			sb.AppendLine();
			sb.AppendLine("\t\t\tif (HasParentCollection)");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine("\t\t\t\tAttachParentContext(ParentCollection.Context);");
			sb.AppendLine("\t\t\t\tParentCollection.Remove(this);");
			sb.AppendLine("\t\t\t}");
			sb.AppendLine("\t\t\telse");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine(string.Format("\t\t\t\tif (!Context.{0}.Contains(LinqEntity))", entityMeta.LinqEntityCollectionName));
			sb.AppendLine(string.Format("\t\t\t\t\tContext.{0}.Attach(LinqEntity, HasChanges);", entityMeta.LinqEntityCollectionName));
			sb.AppendLine();
			sb.AppendLine(string.Format("\t\t\t\tContext.{0}.DeleteOnSubmit(LinqEntity);", entityMeta.LinqEntityCollectionName));
			sb.AppendLine("\t\t\t}");
			sb.AppendLine();
			sb.AppendLine("\t\t\tContextSubmitChanges();");
			sb.AppendLine();
			sb.AppendLine("\t\t\tif (OnAfterDelete != null) { OnAfterDelete(this); }");
			sb.AppendLine("\t\t}");
			sb.AppendLine();
			sb.AppendLine("\t\tprivate void ContextSubmitChanges()");
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tif (!HasParentContext) { Context.SubmitChanges(); }");
			sb.AppendLine("\t\t}");
			sb.AppendLine();
			sb.AppendLine("\t\t#endregion");
		}

		private void FormatIValidateableSection(StringBuilder sb)
		{
			sb.AppendLine("\t\t#region IValidateable Members");
			sb.AppendLine();
			sb.AppendLine("\t\tpublic void ClearValidationErrors()");
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tValidationErrors.Clear();");
			sb.AppendLine("\t\t}");
			sb.AppendLine();
			sb.AppendLine("\t\tpublic bool HasValidationErrors");
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tget { return (ValidationErrors.Count > 0); }");
			sb.AppendLine("\t\t}");
			sb.AppendLine();
			sb.AppendLine("\t\tpublic List<IValidator> Validators");
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tget { return _validators; }");
			sb.AppendLine("\t\t}");
			sb.AppendLine("\t\tprivate List<IValidator> _validators = new List<IValidator>();");
			sb.AppendLine();
			sb.AppendLine("\t\tpublic void Validate()");
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tforeach (var v in Validators)");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine("\t\t\t\tv.Validate(this);");
			sb.AppendLine("\t\t\t}");
			sb.AppendLine();
			sb.AppendLine("\t\t\tif (HasValidationErrors)");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine("\t\t\t\tvar errorMessages = string.Empty;");
			sb.AppendLine();
			sb.AppendLine("\t\t\t\tforeach (var v in ValidationErrors)");
			sb.AppendLine("\t\t\t\t\terrorMessages += string.Format(\"{0}\", v.GetValidationErrorMessage());");
			sb.AppendLine();
			sb.AppendLine("\t\t\t\tif (ValidationErrorHandling == ValidationErrorHandling.ThrowException)");
			sb.AppendLine("\t\t\t\t\tthrow new ValidationException(errorMessages);");
			sb.AppendLine("\t\t\t}");
			sb.AppendLine("\t\t}");
			sb.AppendLine();
			sb.AppendLine("\t\tpublic ValidationErrorHandling ValidationErrorHandling");
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tget");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine("\t\t\t\treturn _validationErrorHandling;");
			sb.AppendLine("\t\t\t}");
			sb.AppendLine("\t\t\tset");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine("\t\t\t\t_validationErrorHandling = value;");
			sb.AppendLine("\t\t\t}");
			sb.AppendLine("\t\t}");
			sb.AppendLine("\t\tprivate ValidationErrorHandling _validationErrorHandling = ValidationErrorHandling.ThrowException;");
			sb.AppendLine();
			sb.AppendLine("\t\tpublic List<IValidationError> ValidationErrors");
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tget { return _validationErrors; }");
			sb.AppendLine("\t\t}");
			sb.AppendLine("\t\tprivate List<IValidationError> _validationErrors = new List<IValidationError>();");
			sb.AppendLine();
			sb.AppendLine("\t\t#endregion");
		}

		private void FormatCollectionSection(StringBuilder sb, EntityMeta entityMeta, ContextMeta meta, bool makeSerializable)
		{
			Console.Write("Generating {0}Collection business entity collection class...", entityMeta.JmrBusinessClassName);
			
			if (makeSerializable)
				sb.AppendLine("\t[Serializable]");
			
			sb.AppendLine(string.Format("\tpublic partial class {0}Collection : Collection<{0}>, IEntityCollectionActiveRecord<{0}>", entityMeta.JmrBusinessClassName));
			sb.AppendLine("\t{");
			sb.AppendLine(string.Format("\t\tpublic {0}Collection() {{ }}", entityMeta.JmrBusinessClassName));
			sb.AppendLine();
			FormatClassContextSection(sb, meta);
			sb.AppendLine();
			FormatIEntityCollectionSection(sb, entityMeta, meta);
			sb.AppendLine("\t}");

			Console.WriteLine("Done!");
		}

		private void FormatIEntityCollectionSection(StringBuilder sb, EntityMeta meta, ContextMeta ctxmeta)
		{
			sb.AppendLine(string.Format("\t\t#region IEntityCollection<{0}> Members", meta.JmrBusinessClassName));
			sb.AppendLine();
			sb.AppendLine("\t\tpublic bool HasParentContext { get; internal set; }");
			sb.AppendLine();
			sb.AppendLine(string.Format("\t\tpublic event EntityCollectionEventHander<{0}> OnBeforeSave;", meta.JmrBusinessClassName));
			sb.AppendLine();
			sb.AppendLine(string.Format("\t\tpublic event EntityCollectionEventHander<{0}> OnAfterSave;", meta.JmrBusinessClassName));
			sb.AppendLine();
			sb.AppendLine(string.Format("\t\tinternal void AttachParentContext({0} context)", ctxmeta.ContextClassName));
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tContext = context;");
			sb.AppendLine("\t\t\tHasParentContext = true;");
			sb.AppendLine("\t\t}");
			sb.AppendLine();
			sb.AppendLine("\t\tinternal void DetachParentContext()");
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tContext = null;");
			sb.AppendLine("\t\t\tHasParentContext = false;");
			sb.AppendLine("\t\t}");
			sb.AppendLine();
			sb.AppendLine(string.Format("\t\tpublic virtual void AddRange(IEnumerable<{0}> enumerable)", meta.JmrBusinessClassName));
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tforeach (var item in enumerable)");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine("\t\t\t\tAdd(item);");
			sb.AppendLine("\t\t\t}");
			sb.AppendLine("\t\t}");
			sb.AppendLine();
			sb.AppendLine(string.Format("\t\tpublic static {0}Collection CreateInstance()", meta.JmrBusinessClassName));
			sb.AppendLine("\t\t{");
			sb.AppendLine(string.Format("\t\t\t{0}Collection ret = new {0}Collection();", meta.JmrBusinessClassName));
			sb.AppendLine(string.Format("\t\t\t{0} ctx = new {0}();", ctxmeta.ContextClassName));
			sb.AppendLine("\t\t\tret.Context = ctx;");
			sb.AppendLine(string.Format("\t\t\tforeach (var linqEntity in ctx.{0})", meta.LinqEntityCollectionName));
			sb.AppendLine("\t\t\t{");
			sb.AppendLine(string.Format("\t\t\t\tvar entity = new {0}();", meta.JmrBusinessClassName));
			sb.AppendLine("\t\t\t\tentity.LinqEntity = linqEntity;");
			sb.AppendLine("\t\t\t\tret.Add(entity);");
			sb.AppendLine("\t\t\t}");
			sb.AppendLine("\t\t\treturn ret;");
			sb.AppendLine("\t\t}");
			sb.AppendLine();
			sb.AppendLine("\t\tpublic virtual void Save()");
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tif (OnBeforeSave != null) { OnBeforeSave(this); }");
			sb.AppendLine();
			sb.AppendLine("\t\t\tforeach (var item in this)");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine("\t\t\t\tif (HasParentContext)");
			sb.AppendLine("\t\t\t\t\titem.AttachParentContext(Context);");
			sb.AppendLine();
			sb.AppendLine("\t\t\t\titem.Save();");
			sb.AppendLine();
			sb.AppendLine("\t\t\t\tif (HasParentContext)");
			sb.AppendLine("\t\t\t\t\titem.DetachParentContext();");
			sb.AppendLine("\t\t\t}");
			sb.AppendLine();
			sb.AppendLine("\t\t\tforeach (var item in _deleteList)");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine(string.Format("\t\t\t\tif (!Context.GetChangeSet().Deletes.Contains(item.LinqEntity))", meta.LinqEntityCollectionName));
			sb.AppendLine(string.Format("\t\t\t\t\tContext.{0}.Attach(item.LinqEntity, item.HasChanges);", meta.LinqEntityCollectionName));
			sb.AppendLine();
			sb.AppendLine(string.Format("\t\t\t\tContext.{0}.DeleteOnSubmit(item.LinqEntity);", meta.LinqEntityCollectionName));
			sb.AppendLine("\t\t\t}");
			sb.AppendLine();
			sb.AppendLine("\t\t\tContextSubmitChanges();");
			sb.AppendLine();
			sb.AppendLine("\t\t\tif (OnAfterSave != null) { OnAfterSave(this); }");
			sb.AppendLine("\t\t}");
			sb.AppendLine();
			sb.AppendLine(string.Format("\t\tprivate List<{0}> _deleteList = new List<{0}>();", meta.JmrBusinessClassName));
			sb.AppendLine();
			sb.AppendLine("\t\tprotected override void RemoveItem(int index)");
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tvar entity = this[index];");
			sb.AppendLine("\t\t\t_deleteList.Add(entity);");
			sb.AppendLine("\t\t\tbase.RemoveItem(index);");
			sb.AppendLine("\t\t}");
			sb.AppendLine();
			sb.AppendLine("\t\tpublic new void RemoveAt(int index)");
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tvar entity = this[index];");
			sb.AppendLine("\t\t\t_deleteList.Add(entity);");
			sb.AppendLine("\t\t\tbase.RemoveItem(index);");
			sb.AppendLine("\t\t}");
			sb.AppendLine();
			sb.AppendLine(string.Format("\t\tpublic new void Remove({0} item)", meta.JmrBusinessClassName));
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tvar index = IndexOf(item);");
			sb.AppendLine("\t\t\t_deleteList.Add(item);");
			sb.AppendLine("\t\t\tbase.RemoveItem(index);");
			sb.AppendLine("\t\t}");
			sb.AppendLine();
			sb.AppendLine(string.Format("\t\tpublic new void Add({0} item)", meta.JmrBusinessClassName));
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\titem.ParentCollection = this;");
			sb.AppendLine("\t\t\titem.Context = Context;");
			sb.AppendLine("\t\t\tbase.Add(item);");
			sb.AppendLine("\t\t}");
			sb.AppendLine();
			sb.AppendLine("\t\tprivate void ContextSubmitChanges()");
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tif (!HasParentContext) { Context.SubmitChanges(); }");
			sb.AppendLine("\t\t}");
			sb.AppendLine();
			sb.AppendLine("\t\t#endregion");
		}

		private void FormatFunctionClassSection(StringBuilder sb, FunctionMeta funcMeta, ContextMeta contextMeta, bool makeSerializable)
		{
			Console.Write("Generating {0} entity class...", funcMeta.JmrElementTypeName);
			
			if (!string.IsNullOrEmpty(funcMeta.LinqEntityIdRef)) { return; }
			
			if (makeSerializable)
				sb.AppendLine("\t[Serializable]");

			sb.AppendLine(string.Format("\tpublic partial class {0}", funcMeta.JmrElementTypeName));
			sb.AppendLine("\t{");
			sb.AppendLine(string.Format("\t\tpublic {0}() {{ }}", funcMeta.JmrElementTypeName));
			sb.AppendLine();
			sb.AppendLine(string.Format("\t\tprivate {0} _linqEntity = null;", funcMeta.LinqElementTypeName));
			sb.AppendLine(string.Format("\t\tinternal {0} LinqEntity", funcMeta.LinqElementTypeName));
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tget");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine("\t\t\t\tif (_linqEntity == null)");
			sb.AppendLine(string.Format("\t\t\t\t\t_linqEntity = new {0}();", funcMeta.LinqElementTypeName));
			sb.AppendLine();
			sb.AppendLine("\t\t\t\treturn _linqEntity;");
			sb.AppendLine("\t\t\t}");
			sb.AppendLine("\t\t\tset");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine("\t\t\t\t_linqEntity = value;");
			sb.AppendLine("\t\t\t}");
			sb.AppendLine("\t\t}");
			sb.AppendLine();

			foreach (var prop in funcMeta.Properties)
			{
				FormatClassPropertySection(sb, prop, false);
				sb.AppendLine();
			}

			sb.AppendLine("\t}");

			Console.WriteLine("Done!");
		}

		private void FormatFunctionCollectionSection(StringBuilder sb, FunctionMeta funcMeta, ContextMeta contextMeta, bool makeSerializable)
		{
			Console.Write("Generating {0} entity class...", funcMeta.JmrMethodName);
			
			if (!string.IsNullOrEmpty(funcMeta.LinqEntityIdRef)) 
			{
				funcMeta.JmrElementTypeName = (from e in contextMeta.Entities
																			where e.LinqIdRef == funcMeta.LinqEntityIdRef
																			select e).Single().JmrBusinessClassName;
			}
			
			if (makeSerializable)
				sb.AppendLine("\t[Serializable]");

			sb.AppendLine(string.Format("\tpublic partial class {0} : Collection<{1}>", funcMeta.JmrMethodName, funcMeta.JmrElementTypeName));
			sb.AppendLine("\t{");
			sb.AppendLine(string.Format("\t\tpublic {0}() {{ }}", funcMeta.JmrMethodName));
			sb.AppendLine();
			sb.AppendLine("\t\t[NonSerialized]");
			sb.AppendLine(string.Format("\t\tprivate {0} _ctx = null;", contextMeta.ContextClassName));
			sb.AppendLine(string.Format("\t\tpublic {0} Context", contextMeta.ContextClassName));
			sb.AppendLine("\t\t{");
			sb.AppendLine("\t\t\tget");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine("\t\t\t\tif (_ctx == null)");
			sb.AppendLine(string.Format("\t\t\t\t\t_ctx = new {0}();", contextMeta.ContextClassName));
			sb.AppendLine();
			sb.AppendLine("\t\t\t\treturn _ctx;");
			sb.AppendLine("\t\t\t}");
			sb.AppendLine("\t\t\tset");
			sb.AppendLine("\t\t\t{");
			sb.AppendLine("\t\t\t\t_ctx = value;");
			sb.AppendLine("\t\t\t}");
			sb.AppendLine("\t\t}");
			sb.AppendLine();

			var parmSignatureString = string.Empty;
			var parmRunString = string.Empty;
			foreach (var parm in funcMeta.Parameters)
			{
				parmSignatureString += string.Format("{0} {1}, ", parm.ClrType, parm.JmrParameterName);
				parmRunString += string.Format("{0}, ", parm.JmrParameterName);
			}

			if (!string.IsNullOrEmpty(parmSignatureString))
				parmSignatureString = parmSignatureString.Substring(0, parmSignatureString.Length - 2);
			if (!string.IsNullOrEmpty(parmRunString))
				parmRunString = parmRunString.Substring(0, parmRunString.Length - 2);

			sb.AppendLine(string.Format("\t\tpublic virtual void Load({0})", parmSignatureString));
			sb.AppendLine("\t\t{");
			sb.AppendLine(string.Format("\t\t\tforeach (var linqEntity in Context.{0}({1}))", funcMeta.LinqMethodName, parmRunString));
			sb.AppendLine("\t\t\t{");
			sb.AppendLine(string.Format("\t\t\t\tvar entity = new {0}();", funcMeta.JmrElementTypeName));
			sb.AppendLine("\t\t\t\tentity.LinqEntity = linqEntity;");
			sb.AppendLine("\t\t\t\tAdd(entity);");
			sb.AppendLine("\t\t\t}");
			sb.AppendLine("\t\t}");
			sb.AppendLine("\t}");

			Console.WriteLine("Done!");
		}
	}
}
